const std = @import("std");
const builtin = @import("builtin");
const options = @import("options");
const os = @import("os.zig");
const color = os.lib.color;
const uart = os.driver.uart;
const Writer = std.Io.Writer;

pub const StackIterator = @import("debug/StackIterator.zig");

var lock: os.Lock.Spin = .init("print");

var writer: Writer = .{
    .vtable = &.{ .drain = drain, .flush = Writer.noopFlush },
    .buffer = &.{},
};

fn puts(_: void, string: []const u8) !usize {
    for (string) |char| uart.putc(char);
    return string.len;
}

fn drain(io_w: *Writer, data: []const []const u8, splat: usize) !usize {
    return os.lib.drain({}, puts, io_w, data, splat) catch unreachable;
}

pub fn print(comptime format: []const u8, args: anytype) void {
    if (!uart.panicking) lock.acquire();
    defer if (!uart.panicking) lock.release();

    writer.print(format, args) catch unreachable;
}

pub fn log(
    comptime level: std.log.Level,
    comptime scope: @EnumLiteral(),
    comptime format: []const u8,
    args: anytype,
) void {
    if (os.is_test and level == .debug) return;
    const scope_color: color = switch (level) {
        .err => .red,
        .warn => .yellow,
        .info => .green,
        .debug => .grey,
    };
    const prefix = std.fmt.comptimePrint(
        "{f}({t}):{f} ",
        .{ comptime scope_color.with("rb"), if (scope == .default) level else scope, color.reset },
    );
    print(prefix ++ format ++ "\n", args);
}

pub const panic = std.debug.FullPanic(panicImpl);

fn panicImpl(msg: []const u8, ra: ?usize) noreturn {
    @branchHint(.cold);
    if (uart.panicking) {
        print("panicked during kernel panic: {s}", .{msg});
        os.proc.shutdown(2);
    }
    uart.panicking = true;

    print("{f}KERNEL PANIC: {f}{s}!\n", .{ color.red.with("r"), color.red.with("ui"), msg });

    if (!builtin.strip_debug_info) {
        const arch = if (os.is_64_bit) "" else " -Darch=riscv32";
        const mode = if (builtin.mode == .Debug) "" else " -Doptimize=" ++ @tagName(builtin.mode);
        const strip = if (builtin.mode == .ReleaseSmall) " -Dstrip=false" else "";
        const sbi = if (!options.m_mode) " -Dsbi" else "";
        const tests = if (os.is_test) " -Dtest" else "";
        const build_options = arch ++ mode ++ strip ++ sbi ++ tests;

        print(
            \\{f}Use the following command to get information about the stack trace:
            \\  {f}zig build{f}{s}{[1]f} addr2line --
        , .{ color.reset, color.magenta.with("rbi"), color.grey.with("ri"), build_options });

        var wait_for: ?usize = ra orelse @returnAddress();
        var printed_any_frame = false;
        var it: StackIterator = .init();

        while (true) switch (it.next()) {
            .end => break,
            .frame => |ret_addr| {
                if (wait_for) |target| {
                    if (ret_addr != target) continue;
                    wait_for = null;
                }
                print(" 0x{x}", .{ret_addr});
                printed_any_frame = true;
            },
        };

        if (!printed_any_frame) {
            print(" (empty stack trace)", .{});
        }

        print("\n{f}", .{color.reset});
    }

    os.proc.shutdown(1);
}
